package com.network.util.snmp4j;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.PDUv1;
import org.snmp4j.ScopedPDU;
import org.snmp4j.Snmp;
import org.snmp4j.Target;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.AbstractTransportMapping;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;

import com.network.util.snmp4j.exception.SnmpAddressException;
import com.sun.org.apache.bcel.internal.util.ClassPath;

public final class SnmpGet
{
	private HashMap<String, String> name2OidMap = null;
	private HashMap<String, String> oid2NameMap = null;
	private Pattern oidRegex = Pattern.compile("[0-9.]+");
	private int timeOut = 5000;
	private int retries = 2;
	private static final Logger logger = Logger.getLogger(SnmpGet.class.getName());

	//===============================================
	private Snmp newSnmpSession(Target target) throws IOException
	{
		AbstractTransportMapping transport = null;
		Address address = target.getAddress();
		if (address instanceof UdpAddress)
		{
			transport = new DefaultUdpTransportMapping();
		}
		else if (address instanceof TcpAddress)
		{
			transport = new DefaultTcpTransportMapping();
		}
		return new Snmp(transport);
	}

	private String[] getOidList(String oidList[])
	{
		ArrayList<String> oidListTemp = new ArrayList<String>();
		for (String oidTemp : oidList)
		{
			Matcher oidMatch = oidRegex.matcher(oidTemp);
			if (oidMatch.matches() == false && name2OidMap != null)
			{
				oidTemp = name2OidMap.get(oidTemp);
			}
			if (oidTemp != null && oidTemp.equals("") == false)
			{
				oidListTemp.add(oidTemp);
			}
		}
		String rtnValue[] = new String[oidListTemp.size()];
		oidListTemp.toArray(rtnValue);
		return rtnValue;
	}

	//	===============================================
	public static SnmpGet getInstance(String cfgFile,String suffix)
	{
		SnmpGet snmpGet = new SnmpGet();
		try
		{
			ClassPath cp = new ClassPath(System.getProperty("java.class.path"));
			String suff = suffix.trim();
			if (suff.startsWith(".") == false)
			{
				suff = "." + suff;
			}
			String fname = cfgFile.trim();
			if (fname.endsWith(".") == true)
			{
				fname = fname.substring(0, fname.length() - 1);
			}
			InputStream is = cp.getInputStream(fname, suff);
			Properties prop = new Properties();
			prop.load(is);
			try
			{
				snmpGet.setTimeOut(Integer.valueOf(prop.getProperty("snmp.get.timeout")));
			}
			catch (NumberFormatException ex)
			{
				logger.error(ex.getLocalizedMessage());
				ex.printStackTrace();
			}
			try
			{
				snmpGet.setRetries(Integer.valueOf(prop.getProperty("snmp.get.retries")));
			}
			catch (NumberFormatException ex)
			{
				logger.error(ex.getLocalizedMessage());
				ex.printStackTrace();
			}
			is.close();
		}
		catch (FileNotFoundException ex)
		{
			logger.error(ex.getLocalizedMessage());
			ex.printStackTrace();
		}
		catch (IOException ex)
		{
			logger.error(ex.getLocalizedMessage());
			ex.printStackTrace();
		}
		return snmpGet;
	}
	
	//	===============================================
	public final PDU newPdu(int snmpVersion)
	{
		PDU rtnCode = null;
		switch (snmpVersion)
		{
			case SnmpConstants.version1:
			{
				rtnCode = new PDUv1();
				rtnCode.setType(PDU.GETNEXT);
				break;
			}
			case SnmpConstants.version2c:
			{
				rtnCode = new PDU();
				rtnCode.setType(PDU.GETNEXT);
				break;
			}
			case SnmpConstants.version3:
			{
				rtnCode = new ScopedPDU();
				rtnCode.setType(PDU.GETBULK);
				break;
			}
			default:
				break;
		}
		return rtnCode;
	}

	public final boolean isReadAble(Target target) throws IOException
	{
		boolean rtnCode = false;
		ArrayList<String> oidList = new ArrayList<String>();
		oidList.add("1.3.6.1.2.1.1.2");
		if (query(oidList, target) != null)
		{
			rtnCode = true;
		}
		return rtnCode;
	}

	public final SnmpGetResultSet query(ArrayList<String> oidList, Target target) throws IOException
	{
		if (oidList == null || oidList.size() == 0)
		{
			return null;
		}
		if (target == null || target.getAddress() == null)
		{
			return null;
		}
		String oidListTemp[] = new String[oidList.size()];
		oidList.toArray(oidListTemp);
		return query(oidListTemp, target);
	}
	
	public final SnmpGetResultSet query(ArrayList<String> oidList, Target target, List<String> commList) throws IOException
	{
		if (oidList == null || oidList.size() == 0)
		{
			return null;
		}
		if (target == null || target.getAddress() == null)
		{
			return null;
		}
		String oidListTemp[] = new String[oidList.size()];
		oidList.toArray(oidListTemp);
		return query(oidListTemp, target, commList);
	}

	public final SnmpGetResultSet query(String oidListInput[], Target target, List<String> commList) throws IOException
	{
		if (oidListInput == null || oidListInput.length == 0)
		{
			return null;
		}
		if (target == null || target.getAddress() == null)
		{
			return null;
		}
		PDU request = null;
		String preOid = "";
		request = newPdu(target.getVersion());
		Snmp snmpSession = newSnmpSession(target);
		snmpSession.listen();
		ResponseEvent responseEvent = null;
		target.setTimeout(this.timeOut);
		target.setRetries(this.retries);
		
		LinkedHashMap<String, String[]> oidValueMap = new LinkedHashMap<String, String[]>();
		String oidList[] = getOidList(oidListInput);
		for (int index = 0; index < oidList.length; index++)
		{
			String oldOid = oidList[index];
			int oldOidLen = oldOid.length() + 1;
			request.clear();
			request.add(new VariableBinding(new OID(oldOid)));
			Date d = new Date();
			Date d2 = new Date();
			while (true && d2.getTime() - d.getTime() < 1 *60* 1000)
			{
				responseEvent = snmpSession.send(request, target);
				if (responseEvent == null || responseEvent.getResponse() == null)
				{
//					continue;
					break;
				}
				PDU response = responseEvent.getResponse();
				if (response.getErrorStatus() != SnmpConstants.SNMP_ERROR_SUCCESS)
				{
//					continue;
					break;
				}
				String curOid = response.get(0).getOid().toString();
				if (curOid.startsWith(oldOid) == false || curOid.equals(preOid))
				{
//					continue;
					break;
				}
				String oidIndex = curOid.substring(oldOidLen);
				String oidValueRow[] = oidValueMap.get(oidIndex);
				if (oidValueRow == null)
				{
					oidValueRow = new String[oidList.length];
					for (int i = 0; i < oidValueRow.length; i++)
					{
						oidValueRow[i] = "";
					}
					oidValueMap.put(oidIndex, oidValueRow);
				}
				oidValueRow[index] = response.get(0).getVariable().toString();
				request.get(0).setOid(response.get(0).getOid());
				preOid = curOid;
				d2 = new Date();
			}
		}
		snmpSession.close();
		String comm = "";
		CommunityTarget Temp = (CommunityTarget) target;
		comm = Temp.getCommunity().toString();
		if(commList!=null && commList.size() > 0)
		{
			List<String> errorVlan = new ArrayList<String>();
			for(String vlan: commList)
			{
				if( errorVlan.contains(vlan)){
					continue;
				}
				if (target instanceof CommunityTarget)
				{
					CommunityTarget objTemp = (CommunityTarget) target;
					objTemp.setCommunity(new OctetString(comm + "@" + vlan));
					request = null;
					preOid = "";
					request = newPdu(target.getVersion());
					snmpSession = newSnmpSession(target);
					snmpSession.listen();
					responseEvent = null;
					target.setTimeout(this.timeOut);
					target.setRetries(this.retries);
					
					//oidValueMap = new LinkedHashMap<String, String[]>();
					oidList = getOidList(oidListInput);
					boolean isErrorVlan = false;
					for (int index = 0; index < oidList.length; index++)
					{
						String oldOid = oidList[index];
						int oldOidLen = oldOid.length() + 1;
						request.clear();
						request.add(new VariableBinding(new OID(oldOid)));
						Date d = new Date();
						Date d2 = new Date();
						if( isErrorVlan ){
							break;
						}
						int count = 0;
						int errorCount = 0;
						while (true && d2.getTime() - d.getTime() <80* 1000)
						{	
							isErrorVlan =false;
							responseEvent = snmpSession.send(request, target);
							if (responseEvent == null || responseEvent.getResponse() == null)
							{
								errorCount++;
								errorVlan.add(vlan);
								if( count == 0){
								  isErrorVlan = true;
								}

								if( errorCount <3){
									continue;
								}
								else{
	//								continue;
									break;
								}
							}
							
							PDU response = responseEvent.getResponse();
							if (response.getErrorStatus() == SnmpConstants.SNMP_ERROR_COMMIT_FAILED)
							{
								errorVlan.add(vlan);
								isErrorVlan = true;
								errorCount++;
								if( errorCount <3){
								continue;
								}
								else{
	//								continue;
									break;
								}
							}
							else if (response.getErrorStatus() != SnmpConstants.SNMP_ERROR_SUCCESS)
							{
								errorVlan.add(vlan);
								if( count == 0){
								  isErrorVlan = true;
								}
								errorCount++;							
								if( errorCount <3){
									continue;
								}
								else{
	//								continue;
									break;
								}
							}
							
							String curOid = response.get(0).getOid().toString();
							if (curOid.startsWith(oldOid) == false || curOid.equals(preOid))
							{
//								continue;
								break;
							}
							String oidIndex = curOid.substring(oldOidLen);
							String oidValueRow[] = oidValueMap.get(oidIndex);
							if (oidValueRow == null)
							{
								oidValueRow = new String[oidList.length];
								for (int i = 0; i < oidValueRow.length; i++)
								{
									oidValueRow[i] = "";
								}
								oidValueMap.put(oidIndex, oidValueRow);
							}
							oidValueRow[index] = response.get(0).getVariable().toString();
							request.get(0).setOid(response.get(0).getOid());
							preOid = curOid;
							count ++ ;
							d2 = new Date();
						}
					}
					snmpSession.close();
				}
			}
			if( errorVlan.size() >0){
				logger.info("errorvlan:" + errorVlan);
				commList.removeAll(errorVlan);
			}
		}
		
		SnmpGetResultSet queryResultSet = null;
		if (oidValueMap.size() > 0)
		{
			queryResultSet = new SnmpGetResultSet();
			queryResultSet.setQueryResult(oidValueMap);
			HashMap<String, Integer> oidIndexMap = new HashMap<String, Integer>();
			for (int i = 0; i < oidList.length; i++)
			{
				String oidTemp = oidList[i];
				oidIndexMap.put(oidTemp, i);
				if (oid2NameMap != null)
				{
					oidTemp = oid2NameMap.get(oidTemp);
					if (oidTemp != null && oidTemp.length() != 0)
					{
						oidIndexMap.put(oidTemp, i);
					}
				}
			}
			queryResultSet.setOidIndexMap(oidIndexMap);
		}
		CommunityTarget temp = (CommunityTarget) target;
		temp.setCommunity(new OctetString(comm));
		return queryResultSet;
	}
	
	public final SnmpGetResultSet query(String oidListInput[], Target target) throws IOException
	{
		if (oidListInput == null || oidListInput.length == 0)
		{
			return null;
		}
		if (target == null || target.getAddress() == null)
		{
			return null;
		}
		PDU request = null;
		String preOid = "";
		request = newPdu(target.getVersion());
		Snmp snmpSession = newSnmpSession(target);
		snmpSession.listen();
		ResponseEvent responseEvent = null;
		target.setTimeout(this.timeOut);
		target.setRetries(this.retries);
		
		LinkedHashMap<String, String[]> oidValueMap = new LinkedHashMap<String, String[]>();
		String oidList[] = getOidList(oidListInput);
		for (int index = 0; index < oidList.length; index++)
		{
			String oldOid = oidList[index];
			int oldOidLen = oldOid.length() + 1;
			request.clear();
			request.add(new VariableBinding(new OID(oldOid)));
			Date d = new Date();
			Date d2 = new Date();
			while (true && d2.getTime() - d.getTime() < 1 *60* 1000)
			{
				responseEvent = snmpSession.send(request, target);
				if (responseEvent == null || responseEvent.getResponse() == null)
				{
//					continue;
					break;
				}
				PDU response = responseEvent.getResponse();
				if (response.getErrorStatus() != SnmpConstants.SNMP_ERROR_SUCCESS)
				{
//					continue;
					break;
				}
				String curOid = response.get(0).getOid().toString();
				if(oldOid.endsWith(".6527.3.1.2.11.1.6.1.10"))
				{
					System.out.println(curOid);
					System.out.println("----------" + response.get(0).getVariable().toString());
				}
//				if (curOid.startsWith(oldOid) == false || curOid.equals(preOid))
//				{
//					break;
//				}
				if (curOid.startsWith(oldOid) == false)
				{
//					continue;
					break;
				}
				String oidIndex = curOid.substring(oldOidLen);
				String oidValueRow[] = oidValueMap.get(oidIndex);
				if (oidValueRow == null)
				{
					oidValueRow = new String[oidList.length];
					for (int i = 0; i < oidValueRow.length; i++)
					{
						oidValueRow[i] = "";
					}
					oidValueMap.put(oidIndex, oidValueRow);
				}
				
				
				
				if (response.get(0).getVariable() instanceof OctetString && oldOid.equalsIgnoreCase("1.3.6.1.2.1.2.2.1.2")) 
				{
					OctetString str = (OctetString) response.get(0).getVariable();
					oidValueRow[index] = new String(str.getValue(), "GB2312");
				} 
				else 
				{
					oidValueRow[index] = response.get(0).getVariable().toString();
				}
				
				
				
				
//				oidValueRow[index] = response.get(0).getVariable().toString();
				request.get(0).setOid(response.get(0).getOid());
				preOid = curOid;
				d2 = new Date();
			}
		}
		snmpSession.close();
		SnmpGetResultSet queryResultSet = null;
		if (oidValueMap.size() > 0)
		{
			queryResultSet = new SnmpGetResultSet();
			queryResultSet.setQueryResult(oidValueMap);
			HashMap<String, Integer> oidIndexMap = new HashMap<String, Integer>();
			for (int i = 0; i < oidList.length; i++)
			{
				String oidTemp = oidList[i];
				oidIndexMap.put(oidTemp, i);
				if (oid2NameMap != null)
				{
					oidTemp = oid2NameMap.get(oidTemp);
					if (oidTemp != null && oidTemp.length() != 0)
					{
						oidIndexMap.put(oidTemp, i);
					}
				}
			}
			queryResultSet.setOidIndexMap(oidIndexMap);
		}
		return queryResultSet;
	}

	//===================================================
	public synchronized static Address parserAddress(String hostAddress) throws SnmpAddressException
	{
		String hostAddr = hostAddress.toLowerCase();
		if (hostAddr.startsWith("udp:") == false && hostAddr.startsWith("tcp:") == false)
		{
			throw new SnmpAddressException("IpAddress format error[udp|tcp:xxx.xxx.xxx.xxx/port] ==> [" + hostAddress + "]");
		}
		String splits[] = hostAddr.split("[/]");
		if (splits.length == 1)
		{
			hostAddr = hostAddr + "/161";
		}
		else if (splits.length > 2)
		{
			throw new SnmpAddressException("IpAddress format error[udp|tcp:xxx.xxx.xxx.xxx/port] ==> [" + hostAddress + "]");
		}
		return GenericAddress.parse(hostAddr);
	}

	public static void main(String args[]) throws IOException
	{
		String ipAddr = "udp:127.0.0.1";
		String community = "public";
		if (args.length < 3)
		{
			System.out.println("Usage:\tjava com.metarnet.ipnms.util.snmp4j.SnmpGet destAddr community oid1 oid2 ... oidn");
			System.exit(0);
		}
		ipAddr = "udp:" + args[0];
		community = args[1];
		ArrayList<String> oidList = new ArrayList<String>();
		for (int i = 2; i < args.length; i++)
		{
			oidList.add(args[i]);
		}
		HashMap<String, String> oid2NameMap = new HashMap<String, String>();
		/*oid2NameMap.put("1.3.6.1.2.1.1.1", "sysDescr");
		 oid2NameMap.put("1.3.6.1.2.1.1.2", "sysObjectID");
		 oid2NameMap.put("1.3.6.1.2.1.1.3", "sysUpTime");
		 oid2NameMap.put("1.3.6.1.2.1.1.4", "sysContact");
		 oid2NameMap.put("1.3.6.1.2.1.1.5", "sysName");
		 oid2NameMap.put("1.3.6.1.2.1.1.6", "sysLocation");
		 oid2NameMap.put("1.3.6.1.2.1.1.7", "sysServices");
		 oid2NameMap.put("1.3.6.1.2.1.1.8", "sysORLastChange");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.1", "ifIndex");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.2", "ifDescr");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.3", "ifType");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.4", "ifMtu");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.5", "ifSpeed");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.6", "ifPhysAddress");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.7", "ifAdminStatus");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.8", "ifOperStatus");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.9", "ifLastChange");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.10", "ifInOctets");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.11", "ifInUcastPkts");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.12", "ifInNUcastPkts");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.13", "ifInDiscards");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.14", "ifInErrors");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.15", "ifInUnknownProtos");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.16", "ifOutOctets");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.17", "ifOutUcastPkts");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.18", "ifOutNUcastPkts");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.19", "ifOutDiscards");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.20", "ifOutErrors");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.21", "ifOutQLen");
		 oid2NameMap.put("1.3.6.1.2.1.2.2.1.22", "ifSpecific");
		 oid2NameMap.put("1.3.6.1.2.1.4.1", "ipForwarding");
		 oid2NameMap.put("1.3.6.1.2.1.31.1.1.1.1", "ifName");
		 oid2NameMap.put("1.3.6.1.2.1.31.1.1.1.2", "ifInMulticastPkts");*/
		CommunityTarget target = new CommunityTarget();
		//target.setAddress(SnmpGet.parserAddress("udp:202.106.232.1"));
		target.setAddress(SnmpGet.parserAddress(ipAddr));
		//target.setAddress(SnmpGet.parserAddress("udp:192.168.6.1"));
		target.setCommunity(new OctetString(community));
		target.setVersion(SnmpConstants.version1);
		SnmpGet snmp4j = new SnmpGet();
		snmp4j.setOid2NameMap(oid2NameMap);
		//System.out.println(System.currentTimeMillis());
		//ArrayList<String> oidList = IpRouteTable.getOidList();
		//oidList.add("1.3.6.1.2.1.31.1.1.1.2");
		//oidList.add("1.3.6.1.2.1.31.1.1.1.4");
		//oidList.add("1.3.6.1.4.1.9.9.156.1.2.1.1.15");
		SnmpGetResultSet sgrs = snmp4j.query(oidList, target);
		//System.out.println(System.currentTimeMillis());
		if (sgrs != null)
		{
			for (String string : sgrs.getKeySet())
			{
				System.out.print(string + "[");
				String sss[] = sgrs.getQueryResult().get(string);
				for (String string2 : sss)
				{
					System.out.print(string2 + "|");
				}
				System.out.println("]");
			}
			//System.out.println("\n");
			//System.out.println(sgrs.getValue("1.3.6.1.2.1.31.1.1.1.4", 0));
			/*for (ArrayList<String> row : sgrs.getQueryResult())
			 {
			 for (String string : row)
			 {
			 System.out.print(string + "|");
			 }
			 System.out.println();
			 }*/
		}
	}

	public final void setOid2NameMap(HashMap<String, String> snmpOidMap)
	{
		this.oid2NameMap = snmpOidMap;
		if (name2OidMap == null && oid2NameMap != null)
		{
			name2OidMap = new HashMap<String, String>();
			for (String key : oid2NameMap.keySet())
			{
				name2OidMap.put(oid2NameMap.get(key), key);
			}
		}
	}

	public final void setName2OidMap(HashMap<String, String> name2OidMap)
	{
		this.name2OidMap = name2OidMap;
		if (oid2NameMap == null && name2OidMap != null)
		{
			oid2NameMap = new HashMap<String, String>();
			for (String key : name2OidMap.keySet())
			{
				oid2NameMap.put(name2OidMap.get(key), key);
			}
		}
	}

	public int getTimeOut()
	{
		return timeOut;
	}

	public void setTimeOut(int timeOut)
	{
		this.timeOut = timeOut;
	}

	public int getRetries()
	{
		return retries;
	}

	public void setRetries(int retries)
	{
		this.retries = retries;
	}
}
